//! Thin wrapper around the generic resource router tuned for database connections.

use super::{DataSourceManager, RegionalError, RegionalResult};
use price_common::resource::{ResourceContext, ResourceRouter as GenericResourceRouter, ResourceSource};
use rbatis::RBatis;
use std::sync::Arc;
use tracing::debug;

/// Information about where a connection originates from.
#[derive(Debug, Clone)]
pub struct ConnectionSource {
    pub code: String,
    pub key: String,
    pub fallback_level: usize,
}

impl From<ResourceSource> for ConnectionSource {
    fn from(source: ResourceSource) -> Self {
        Self { code: source.code, key: source.key, fallback_level: source.fallback_level }
    }
}

/// Database-specific wrapper around [`GenericResourceRouter`].
pub struct DataSourceRouter {
    router: GenericResourceRouter<RBatis, DataSourceManager>,
}

impl DataSourceRouter {
    pub fn new(manager: Arc<DataSourceManager>) -> Self {
        Self { router: GenericResourceRouter::new(manager) }
    }

    pub async fn get_connection(
        &self,
        context: &ResourceContext,
    ) -> RegionalResult<(Arc<RBatis>, ConnectionSource)> {
        self.router
            .get_resource(context)
            .await
            .map(|(conn, source)| {
                debug!("connection resolved: code={} key={} level={}", source.code, source.key, source.fallback_level);
                (conn, ConnectionSource::from(source))
            })
            .map_err(|err| RegionalError::from_resource_error(err, context, context.current_code()))
    }

    pub async fn get_connections_with_fallback(
        &self,
        context: &ResourceContext,
    ) -> RegionalResult<Vec<(Arc<RBatis>, ConnectionSource)>> {
        let resources = self
            .router
            .get_resources_with_fallback(context)
            .await
            .map_err(|err| RegionalError::from_resource_error(err, context, context.current_code()))?;

        if resources.is_empty() {
            return Err(RegionalError::DataSourceNotFound(context.current_code().to_string(), context.resource_key(context.current_code())));
        }

        Ok(resources
            .into_iter()
            .map(|(conn, source)| {
                debug!("connection resolved: code={} key={} level={}", source.code, source.key, source.fallback_level);
                (conn, ConnectionSource::from(source))
            })
            .collect())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use price_common::resource::{ResourceContext, ResourceHierarchy};

    #[tokio::test]
    async fn router_can_be_created() {
        let manager = Arc::new(DataSourceManager::new());
        let _router = DataSourceRouter::new(manager);
    }

    #[test]
    fn connection_source_fields() {
        let source = ConnectionSource { code: "610100".to_string(), key: "610100".to_string(), fallback_level: 1 };

        assert_eq!(source.code, "610100");
        assert_eq!(source.key, "610100");
        assert_eq!(source.fallback_level, 1);
    }

    #[tokio::test]
    async fn router_returns_error_when_missing() {
        let manager = Arc::new(DataSourceManager::new());
        let router = DataSourceRouter::new(manager);
        let context = ResourceContext::new(ResourceHierarchy::single_level("610100", "code"));
        let result = router.get_connections_with_fallback(&context).await;
        assert!(result.is_err());
    }
}
